package com.xuecheng.orders.controller;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import com.xuecheng.base.constant.OrderAlipayTradeStatusConstant;
import com.xuecheng.base.constant.OrderPayChannelConstant;
import com.xuecheng.base.constant.OrderPayRecordStatusConstant;
import com.xuecheng.base.constant.RabbitMQConstant;
import com.xuecheng.base.exception.XCPlusBaseException;
import com.xuecheng.message.sdk.constant.MqMessageTypeConstant;
import com.xuecheng.message.sdk.model.po.MqMessage;
import com.xuecheng.message.sdk.service.IMqMessageService;
import com.xuecheng.orders.config.AliPayProperties;
import com.xuecheng.orders.exception.OrderPaidException;
import com.xuecheng.orders.exception.OrdersError;
import com.xuecheng.orders.exception.QRCodeExpiredException;
import com.xuecheng.orders.exception.TradeHasBeenClosedException;
import com.xuecheng.orders.model.dto.AddOrderDTO;
import com.xuecheng.orders.model.dto.PayRecordDTO;
import com.xuecheng.orders.model.po.XcOrders;
import com.xuecheng.orders.model.po.XcPayRecord;
import com.xuecheng.orders.service.IOrderService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author 勾新杰
 * @version 1.0
 * @description:
 * @date 2025/3/1 9:13
 */
@Slf4j
@Controller
@RequiredArgsConstructor
@Api(tags = "支付管理接口")
public class OrderController {

    private final IOrderService orderService;

    private final AlipayClient alipayClient;

    private final AliPayProperties aliPayProperties;

    private final RabbitTemplate rabbitTemplate;

    private final IMqMessageService mqMessageService;

    private static final String QUICK_WEBSITE_PHONE_PAY_WAY = "QUICK_WAP_WAY";

    /**
     * 生成支付二维码，二维码的本质就是一个网址，这里就是支付宝支付网址，所以要把订单相关参数也要给前端
     *
     * @param addOrderDTO 添加订单请求参数
     * @return 支付记录和二维码
     */
    @ApiOperation("生成支付二维码")
    @PostMapping("/generatepaycode")
    @ResponseBody
    public PayRecordDTO generatePayCode(@RequestBody @Validated AddOrderDTO addOrderDTO) {
        return orderService.createOrder(addOrderDTO);
    }

    /**
     * 扫码下单
     *
     * @param payNo    支付交易号
     * @param response http响应，构建表单返回
     */
    @ApiOperation("扫码下单接口")
    @GetMapping("/requestpay")
    public void requestpay(String payNo, Long expireAt, HttpServletResponse response) throws AlipayApiException {
        // 1. 检查订单是否过期
        if (expireAt < System.currentTimeMillis()) QRCodeExpiredException.cast();
        // 2. 检查支付记录是否已经支付或者已经关闭，或者已退款
        XcPayRecord payRecord = orderService.getByPayNo(payNo);
        if (payRecord == null) throw new XCPlusBaseException(OrdersError.PAY_AGAIN.getErrMessage());
        if (OrderPayRecordStatusConstant.PAID.equals(payRecord.getStatus())) OrderPaidException.cast();
        if (OrderPayRecordStatusConstant.CLOSED.equals(payRecord.getStatus())) TradeHasBeenClosedException.cast();
        if (OrderPayRecordStatusConstant.REFUNDED.equals(payRecord.getStatus())) throw new XCPlusBaseException(OrdersError.ORDER_REFUND.getErrMessage());
        // 3. 构建支付请求
        AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
        AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
        model.setOutTradeNo(payNo); // 订单号
        model.setTotalAmount(String.valueOf(payRecord.getTotalPrice())); // 金额
        model.setSubject(payRecord.getOrderName()); // 标题
        model.setProductCode(QUICK_WEBSITE_PHONE_PAY_WAY); // 产品码
        request.setBizModel(model);
        request.setReturnUrl(aliPayProperties.getReturnUrl()); // 支付成功后跳转的页面，用户跳转的页面
        request.setNotifyUrl(aliPayProperties.getNotifyUrl()); // 支付成功后异步通知的url，通知的是商家
        // 4. 发送请求
        AlipayTradeWapPayResponse payResponse = alipayClient.pageExecute(request, HttpMethod.POST.name());
        if (payResponse.isSuccess()) {
            response.setContentType(MediaType.TEXT_HTML_VALUE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());
            try {
                // 获取返回的表单返回给前端
                String form = payResponse.getBody();
                System.out.println(form);
                response.getWriter().write(form);
                response.getWriter().flush();
                response.getWriter().close();
            } catch (IOException e) {
                log.error("Failed to write response: " + e.getMessage());
            }
        } else {
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try {
                response.getWriter().write("支付请求失败，请稍后再试。");
                response.getWriter().close();
            } catch (IOException e) {
                log.error("Failed to write response: " + e.getMessage());
            }
        }
    }

    /**
     * 查询支付结果
     *
     * @param payNo 交易流水号
     * @return 支付结果
     */
    @ApiOperation("查询支付结果")
    @GetMapping("/payresult")
    @ResponseBody
    public PayRecordDTO payresult(String payNo) {
        return orderService.queryPayResult(payNo);
    }

    /**
     * 接收支付结果异步通知
     *
     * @param request  请求
     * @param response 响应
     */
    @ApiOperation("接收支付结果异步通知")
    @PostMapping("/receivenotify")
    public void receivenotify(HttpServletRequest request, HttpServletResponse response) {
        // 1. 解析阿里支付的异步通知参数
        Map<String, String> params = new HashMap<>();
        Map<String, String[]> parameterMap = request.getParameterMap();
        parameterMap.forEach((key, value) -> {
            StringBuilder builder = new StringBuilder();
            for (int i = 0; i < value.length - 1; i++) {
                builder.append(value[i]).append(",");
            }
            builder.append(value[value.length - 1]);
            params.put(key, builder.toString());
        });
        try {
            // 2. 验签
            boolean sign = AlipaySignature.rsaCheckV1(params, aliPayProperties.getAlipayPublicKey(), aliPayProperties.getCharset(), aliPayProperties.getSignType());
            if (sign) {
                // 3. 验签通过则看内容是否都合法
                String appId = request.getParameter("app_id");
                String tradeNo = request.getParameter("trade_no");
                String outTradeNo = request.getParameter("out_trade_no");
                String tradeStatus = request.getParameter("trade_status");
                String totalAmount = request.getParameter("total_amount");
                XcPayRecord payRecord = orderService.getByPayNo(outTradeNo);
                if (!aliPayProperties.getAppid().equals(appId)) {
                    log.error("Failed to verify appId: " + appId);
                    response.getWriter().write("failure");
                }
                if (payRecord == null) {
                    log.error("Failed to verify outTradeNo: " + outTradeNo);
                    response.getWriter().write("failure");
                }
                if (String.valueOf(Objects.requireNonNull(payRecord).getTotalPrice()).equals(totalAmount)) {
                    log.error("Failed to verify totalAmount: " + totalAmount);
                    response.getWriter().write("failure");
                }
                // 4. 如果内容都合法，那么更新支付流水表和订单表
                if (tradeStatus.equals(OrderAlipayTradeStatusConstant.TRADE_SUCCESS)
                        || tradeStatus.equals(OrderAlipayTradeStatusConstant.TRADE_FINISHED)) {
                    // 4.1 修改订单表和支付流水表
                    orderService.paySuccess(outTradeNo, tradeNo, OrderPayChannelConstant.ALIPAY);
                    // 4.4 发送支付成功消息
                    sendPaySuccessMessage(outTradeNo, OrderPayRecordStatusConstant.PAID);
                    response.getWriter().write("success");
                } else {
                    log.error("Failed to verify tradeStatus: " + tradeStatus);
                    response.getWriter().write("failure");
                }
            } else {
                // 5. 验签不通过
                response.getWriter().write("failure");
            }
        } catch (AlipayApiException e) {
            log.error("Failed to verify signature: " + e.getMessage());
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("Failed to write response: " + e.getMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * 发送支付成功消息
     *
     * @param outTradeNo 支付流水号
     * @param payStatus  支付状态
     */
    private void sendPaySuccessMessage(String outTradeNo, String payStatus) {
        // 1. 获取订单
        XcPayRecord payRecord = orderService.getByPayNo(outTradeNo);
        XcOrders orders = orderService.getOrderById(payRecord.getOrderId());
        // 2. 获取订单相关的业务id也就是选课id，加入到消息表中，将来生产者确认机制可以将成功的消息写入mq_message_history
        MqMessage mqMessage = mqMessageService.addMessage(MqMessageTypeConstant.MESSAGE_TYPE_PAY_RESULT_NOTIFY, orders.getOutBusinessId(), payStatus, null);
        // 3. 添加生产者确认机制，不建议添加生产者return机制，因为很消耗mq的性能
        CorrelationData correlationData = new CorrelationData();
        correlationData.getFuture().addCallback(new ListenableFutureCallback<CorrelationData.Confirm>() {

            /**
             * 小概率会发生
             * @param ex the failure
             */
            @Override
            public void onFailure(@NotNull Throwable ex) {
                log.error("Failed to send message: " + ex.getMessage());
                mqMessageService.fail(mqMessage.getId(), ex.getMessage());
            }

            /**
             * 消息发送成功
             * @param result the correlation data
             */
            @Override
            public void onSuccess(CorrelationData.Confirm result) {
                if (result.isAck()) { // 确认收到消息并且路由到对应的queue并且持久化
                    mqMessageService.success(mqMessage.getId());
                } else {
                    log.error("Failed to send message: " + result.getReason());
                    mqMessageService.fail(mqMessage.getId(), "Failed to send message.");
                }
            }
        });
        // 4. 发送消息，这里配置routing_key_pay_result_learning是因为将来可能会向其它微服务发送消息，但都是发到同一个交换机，就用topic交换机
        rabbitTemplate.convertAndSend(RabbitMQConstant.EXCHANGE_PAY_RESULT_TOPIC, RabbitMQConstant.ROUTING_KEY_PAY_RESULT_LEARNING, mqMessage, correlationData);
    }
}
